package com.cke.yang.storage.app.service.impl;

import com.cke.yang.storage.api.dto.request.StgFileListRequest;
import com.cke.yang.storage.api.dto.response.FileDownloadResponse;
import com.cke.yang.storage.api.dto.response.StgFileListResponse;
import com.cke.yang.storage.app.service.StgFileService;
import com.cke.yang.storage.domain.entity.StgFile;
import com.cke.yang.storage.domain.entity.StgSource;
import com.cke.yang.storage.infra.mapper.StgFileMapper;
import com.cke.yang.storage.infra.mapper.StgSourceMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * 存储模块-文件应用层实现
 *
 * @author yuyang
 */
@Service
@Slf4j
public class StgFileServiceImpl implements StgFileService {

    /**
     * IP地址前缀优先级列表（越靠前优先级越高）
     */
    private static final List<String> IP_PREFIX_PRIORITY = Arrays.asList("192.168", "172.18", "localhost");

    @Autowired
    private StgFileMapper stgFileMapper;

    @Autowired
    private StgSourceMapper stgSourceMapper;

    @Value("${server.port:8080}")
    private String serverPort;

    /**
     * 自动检测的IP地址或主机名
     */
    private String detectedHost;

    /**
     * 构造方法，初始化时检测IP地址
     */
    public StgFileServiceImpl() {
        this.detectedHost = detectHostAddress();
        log.info("检测到的主机地址: {}", this.detectedHost);
    }

    @Override
    public List<StgFileListResponse> list(StgFileListRequest request) {
        // 构建查询条件
        QueryWrapper<StgFile> queryWrapper = new QueryWrapper<>();

        // 根据当前路径查询文件
        if (StringUtils.hasLength(request.getCurrentPath())) {
            if (!request.getCurrentPath().endsWith("/")) {
                request.setCurrentPath(request.getCurrentPath() + "/");
            }
            queryWrapper.lambda().eq(StgFile::getFilePath, request.getCurrentPath());
        }

        // 查询状态为活跃的文件
        queryWrapper.lambda()
                .eq(StgFile::getStatus, 1)
                .eq(StgFile::getSourceId, request.getSourceId())
                .orderByDesc(StgFile::getFileType)
                .orderByAsc(StgFile::getFileName);

        List<StgFile> stgFiles = stgFileMapper.selectList(queryWrapper);

        // 转换为响应DTO并添加fileUrl和previewUrl
        List<StgFileListResponse> responseList = new ArrayList<>();
        for (StgFile stgFile : stgFiles) {
            StgFileListResponse response = new StgFileListResponse();
            BeanUtils.copyProperties(stgFile, response);

            // 只为非目录类型的文件生成URL
            if (!"directory".equals(stgFile.getFileType())) {
                // 构建文件下载URL
                String baseUrl = "http://" + detectedHost + ":" + serverPort;
                String fileUrl = baseUrl + "/api/storage/stg-file/download?fileKey=" + stgFile.getFileKey() + "&fileName="+stgFile.getFileName();
                response.setFileUrl(fileUrl);

                // 构建文件预览URL
                String fileUrlBase64 = Base64.getEncoder().encodeToString(fileUrl.getBytes());
                // 对fileUrlBase64进行urlEncode编码
                String encodedFileUrlBase64;
                encodedFileUrlBase64 = java.net.URLEncoder.encode(fileUrlBase64, StandardCharsets.UTF_8);

                String previewUrl = "http://" + detectedHost + ":" + serverPort + "/preview/index.html?debug=true&mode=default&keyBase64=" + encodedFileUrlBase64;
                response.setPreviewUrl(previewUrl);
            }

            responseList.add(response);
        }

        return responseList;
    }

    @Override
    public List<StgFile> upload(MultipartFile[] files, Long sourceId, String uploadPathBase64) {
        // 1. 根据sourceId获取存储源信息
        StgSource stgSource = stgSourceMapper.selectById(sourceId);
        if (stgSource == null) {
            throw new RuntimeException("存储源不存在，sourceId: " + sourceId);
        }

        if (stgSource.getIsEnabled() != 1) {
            throw new RuntimeException("存储源未启用，sourceId: " + sourceId);
        }

        // 2. 获取存储路径
        String basePath = stgSource.getBasePath();
        if (!StringUtils.hasText(basePath)) {
            throw new RuntimeException("存储源基础路径未配置，sourceId: " + sourceId);
        }

        // 3. 处理上传路径 - Base64解码
        String uploadPath = decodeUploadPath(uploadPathBase64);

        // 4. 组合最终存储路径
        String absolutePath = buildFinalStoragePath(basePath + "\\data", uploadPath);

        // 5. 确保存储目录存在
        ensureStorageDirectoryExists(absolutePath);

        // 6. 批量处理文件上传
        List<StgFile> uploadedFiles = new ArrayList<>();
        for (MultipartFile file : files) {
            if (file.isEmpty()) {
                log.warn("跳过空文件: {}", file.getOriginalFilename());
                continue;
            }

            try {
                StgFile stgFile = uploadSingleFile(file, stgSource, uploadPath, absolutePath);
                uploadedFiles.add(stgFile);
            } catch (Exception e) {
                log.error("文件上传失败: {}", file.getOriginalFilename(), e);
                throw new RuntimeException("文件上传失败: " + file.getOriginalFilename() + ", " + e.getMessage());
            }
        }

        return uploadedFiles;
    }

    /**
     * 上传单个文件
     *
     * @param file 上传的文件
     * @param stgSource 存储源
     * @param absolutePath 完整存储路径
     * @return 文件记录
     */
    private StgFile uploadSingleFile(MultipartFile file, StgSource stgSource, String uploadPath, String absolutePath) throws IOException, NoSuchAlgorithmException {
        // 1. 生成文件唯一标识
        String fileKey = UUID.randomUUID().toString();
        String originalFilename = file.getOriginalFilename();

        // 2. 构建文件保存路径
        Path targetPath = Paths.get(absolutePath, originalFilename);

        // 3. 保存文件到磁盘
        file.transferTo(targetPath.toFile());

        // 4. 计算文件Hash值
        String hashCode = calculateFileHash(targetPath);

        // 5. 创建文件记录
        StgFile stgFile = new StgFile();
        stgFile.setSourceId(stgSource.getSourceId());
        stgFile.setFileName(originalFilename);
        stgFile.setFileKey(fileKey);
        stgFile.setFilePath(StringUtils.hasLength(uploadPath) ? uploadPath+"/" : "/");
        stgFile.setFileSize(file.getSize());
        stgFile.setHashCode(hashCode);
        // 1:active
        stgFile.setStatus(1);

        // 6. 插入数据库记录
        stgFileMapper.insert(stgFile);

        log.info("文件上传成功: {} -> {}", originalFilename, targetPath);
        return stgFile;
    }

    /**
     * 计算文件Hash值
     *
     * @param filePath 文件路径
     * @return Hash值
     */
    private String calculateFileHash(Path filePath) throws IOException, NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] fileBytes = Files.readAllBytes(filePath);
        byte[] hashBytes = md.digest(fileBytes);

        StringBuilder sb = new StringBuilder();
        for (byte b : hashBytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    /**
     * 确保存储目录存在
     *
     * @param storagePath 存储路径
     */
    private void ensureStorageDirectoryExists(String storagePath) {
        Path storageDir = Paths.get(storagePath);
        try {
            if (!Files.exists(storageDir)) {
                Files.createDirectories(storageDir);
                log.info("创建存储目录成功: {}", storagePath);
            }
        } catch (IOException e) {
            log.error("创建存储目录失败: {}", storagePath, e);
            throw new RuntimeException("创建存储目录失败: " + storagePath);
        }
    }

    /**
     * 构建最终存储路径
     *
     * @param basePath 基础路径
     * @param uploadPath 上传路径
     * @return 最终存储路径
     */
    private String buildFinalStoragePath(String basePath, String uploadPath) {
        if (StringUtils.hasText(uploadPath)) {
            // 如果提供了uploadPath，则组合basePath和uploadPath
            // 确保uploadPath不以/开头，避免路径拼接问题
            String cleanUploadPath = uploadPath.startsWith("/") ? uploadPath.substring(1) : uploadPath;
            return Paths.get(basePath, cleanUploadPath).toString();
        } else {
            // 如果没有提供uploadPath，直接使用basePath
            return basePath;
        }
    }

    /**
     * 解码Base64编码的上传路径
     *
     * @param uploadPathBase64 Base64编码的上传路径
     * @return 解码后的上传路径，如果输入为空则返回null
     */
    private String decodeUploadPath(String uploadPathBase64) {
        if (!StringUtils.hasText(uploadPathBase64)) {
            return null;
        }

        try {
            // Base64解码
            byte[] decodedBytes = Base64.getDecoder().decode(uploadPathBase64);
            String uploadPath = new String(decodedBytes, "UTF-8");
            log.debug("Base64解码成功: {} -> {}", uploadPathBase64, uploadPath);

            // URL解码
            String urlDecodedPath = java.net.URLDecoder.decode(uploadPath, StandardCharsets.UTF_8);
            log.debug("URL解码成功: {} -> {}", uploadPath, urlDecodedPath);

            return urlDecodedPath;
        } catch (Exception e) {
            log.error("Base64解码失败: {}", uploadPathBase64, e);
            throw new RuntimeException("上传路径Base64解码失败: " + e.getMessage());
        }
    }

    @Override
    public FileDownloadResponse downloadByFileKey(String fileKey) {
        // 1. 根据fileKey查询文件记录
        QueryWrapper<StgFile> queryWrapper = new QueryWrapper<>();
        // 只查询活跃状态的文件
        queryWrapper.lambda()
                .eq(StgFile::getFileKey, fileKey)
                .eq(StgFile::getStatus, 1);

        StgFile stgFile = stgFileMapper.selectOne(queryWrapper);
        if (stgFile == null) {
            throw new RuntimeException("文件不存在或已被删除，fileKey: " + fileKey);
        }

        // 2. 根据sourceId获取存储源信息
        StgSource stgSource = stgSourceMapper.selectById(stgFile.getSourceId());
        if (stgSource == null) {
            throw new RuntimeException("存储源不存在，sourceId: " + stgFile.getSourceId());
        }

        if (stgSource.getIsEnabled() != 1) {
            throw new RuntimeException("存储源未启用，sourceId: " + stgFile.getSourceId());
        }

        // 3. 构建完整的文件路径
        String fullFilePath = buildDownloadFilePath(stgSource, stgFile);

        // 4. 检查文件是否存在
        File file = new File(fullFilePath);
        if (!file.exists() || !file.isFile()) {
            throw new RuntimeException("物理文件不存在: " + fullFilePath);
        }

        // 5. 创建文件资源
        Resource resource = new FileSystemResource(file);

        // 6. 确定文件的Content-Type
        String contentType = determineContentType(stgFile.getFileName());

        log.info("文件下载成功: {} -> {}", fileKey, fullFilePath);

        return new FileDownloadResponse(resource, stgFile.getFileName(), contentType, stgFile.getFileSize().longValue());
    }

    /**
     * 构建下载文件的完整路径
     *
     * @param stgSource 存储源
     * @param stgFile 文件记录
     * @return 完整文件路径
     */
    private String buildDownloadFilePath(StgSource stgSource, StgFile stgFile) {
        // 文件实际存储的路径格式: filePath/fileKey_fileName
        String fileName = stgFile.getFilePath() + "\\"+ stgFile.getFileName();
        return Paths.get(stgSource.getBasePath() + "\\data" , fileName).toString();
    }

    /**
     * 根据文件名确定Content-Type
     *
     * @param fileName 文件名
     * @return Content-Type
     */
    private String determineContentType(String fileName) {
        if (fileName == null) {
            return "application/octet-stream";
        }

        String lowerFileName = fileName.toLowerCase();

        // 图片类型
        if (lowerFileName.endsWith(".jpg") || lowerFileName.endsWith(".jpeg")) {
            return "image/jpeg";
        } else if (lowerFileName.endsWith(".png")) {
            return "image/png";
        } else if (lowerFileName.endsWith(".gif")) {
            return "image/gif";
        } else if (lowerFileName.endsWith(".bmp")) {
            return "image/bmp";
        } else if (lowerFileName.endsWith(".webp")) {
            return "image/webp";
        }
        // 文档类型
        else if (lowerFileName.endsWith(".pdf")) {
            return "application/pdf";
        } else if (lowerFileName.endsWith(".doc")) {
            return "application/msword";
        } else if (lowerFileName.endsWith(".docx")) {
            return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
        } else if (lowerFileName.endsWith(".xls")) {
            return "application/vnd.ms-excel";
        } else if (lowerFileName.endsWith(".xlsx")) {
            return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
        } else if (lowerFileName.endsWith(".ppt")) {
            return "application/vnd.ms-powerpoint";
        } else if (lowerFileName.endsWith(".pptx")) {
            return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
        }
        // 文本类型
        else if (lowerFileName.endsWith(".txt")) {
            return "text/plain";
        } else if (lowerFileName.endsWith(".html") || lowerFileName.endsWith(".htm")) {
            return "text/html";
        } else if (lowerFileName.endsWith(".css")) {
            return "text/css";
        } else if (lowerFileName.endsWith(".js")) {
            return "application/javascript";
        } else if (lowerFileName.endsWith(".json")) {
            return "application/json";
        } else if (lowerFileName.endsWith(".xml")) {
            return "application/xml";
        }
        // 压缩文件
        else if (lowerFileName.endsWith(".zip")) {
            return "application/zip";
        } else if (lowerFileName.endsWith(".rar")) {
            return "application/x-rar-compressed";
        } else if (lowerFileName.endsWith(".7z")) {
            return "application/x-7z-compressed";
        }
        // 音视频
        else if (lowerFileName.endsWith(".mp3")) {
            return "audio/mpeg";
        } else if (lowerFileName.endsWith(".mp4")) {
            return "video/mp4";
        } else if (lowerFileName.endsWith(".avi")) {
            return "video/x-msvideo";
        }
        // 默认类型
        else {
            return "application/octet-stream";
        }
    }

    @Override
    public StgFile mkdir(Long sourceId, String directoryName, String parentPath) {
        // 1. 根据sourceId获取存储源信息
        StgSource stgSource = stgSourceMapper.selectById(sourceId);
        if (stgSource == null) {
            throw new RuntimeException("存储源不存在，sourceId: " + sourceId);
        }

        if (stgSource.getIsEnabled() != 1) {
            throw new RuntimeException("存储源未启用，sourceId: " + sourceId);
        }

        // 2. 获取存储路径
        String basePath = stgSource.getBasePath();
        if (!StringUtils.hasText(basePath)) {
            throw new RuntimeException("存储源基础路径未配置，sourceId: " + sourceId);
        }

        // 3. 构建目录的完整路径
        String directoryPath = buildDirectoryPath(basePath + "\\data", parentPath, directoryName);

        // 4. 创建物理目录
        Path dirPath = Paths.get(directoryPath);
        try {
            if (Files.exists(dirPath)) {
                throw new RuntimeException("目录已存在: " + directoryName);
            }
            Files.createDirectories(dirPath);
            log.info("创建目录成功: {}", directoryPath);
        } catch (IOException e) {
            log.error("创建目录失败: {}", directoryPath, e);
            throw new RuntimeException("创建目录失败: " + directoryName + ", " + e.getMessage());
        }

        // 5. 生成目录的唯一标识
        String fileKey = UUID.randomUUID().toString();

        // 6. 构建目录在数据库中的路径（相对路径）
        String dbPath = buildDbPath(parentPath, directoryName);

        // 7. 创建目录记录
        StgFile stgFile = new StgFile();
        stgFile.setSourceId(sourceId);
        stgFile.setFileName(directoryName);
        stgFile.setFileKey(fileKey);
        stgFile.setFilePath(StringUtils.hasText(parentPath) && !"/".equals(parentPath) ? parentPath + "/" : "/");
        stgFile.setFileSize(0L);
        stgFile.setHashCode("");
        stgFile.setFileType("directory");
        stgFile.setContentFormat("");
        stgFile.setMimeType("");
        stgFile.setStatus(1);

        // 8. 插入数据库记录
        stgFileMapper.insert(stgFile);

        log.info("目录记录创建成功: {} -> {}", directoryName, dbPath);
        return stgFile;
    }

    @Override
    public boolean remove(String fileKey) {
        // 1. 根据fileKey查询文件记录
        QueryWrapper<StgFile> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(StgFile::getFileKey, fileKey)
                .eq(StgFile::getStatus, 1);

        StgFile stgFile = stgFileMapper.selectOne(queryWrapper);
        if (stgFile == null) {
            throw new RuntimeException("文件不存在或已被删除，fileKey: " + fileKey);
        }

        // 2. 根据sourceId获取存储源信息
        StgSource stgSource = stgSourceMapper.selectById(stgFile.getSourceId());
        if (stgSource == null) {
            throw new RuntimeException("存储源不存在，sourceId: " + stgFile.getSourceId());
        }

        if (stgSource.getIsEnabled() != 1) {
            throw new RuntimeException("存储源未启用，sourceId: " + stgFile.getSourceId());
        }

        // 3. 如果是目录类型，检查目录下是否有文件
        if ("directory".equals(stgFile.getFileType())) {
            String directoryPath = buildDbPath(stgFile.getFilePath(), stgFile.getFileName());
            QueryWrapper<StgFile> childQueryWrapper = new QueryWrapper<>();
            childQueryWrapper.lambda()
                    .eq(StgFile::getSourceId, stgFile.getSourceId())
                    .eq(StgFile::getFilePath, directoryPath)
                    .eq(StgFile::getStatus, 1);
            Long childCount = stgFileMapper.selectCount(childQueryWrapper);
            if (childCount > 0) {
                throw new RuntimeException("目录不为空，无法删除: " + stgFile.getFileName());
            }
        }

        // 4. 构建完整的文件路径
        String fullFilePath = buildDownloadFilePath(stgSource, stgFile);

        // 5. 删除物理文件
        File file = new File(fullFilePath);
        if (file.exists()) {
            try {
                if ("directory".equals(stgFile.getFileType())) {
                    Files.delete(Paths.get(fullFilePath));
                    log.info("物理目录删除成功: {}", fullFilePath);
                } else {
                    if (!file.delete()) {
                        log.warn("物理文件删除失败: {}", fullFilePath);
                    } else {
                        log.info("物理文件删除成功: {}", fullFilePath);
                    }
                }
            } catch (IOException e) {
                log.error("删除物理文件失败: {}", fullFilePath, e);
                throw new RuntimeException("删除物理文件失败: " + e.getMessage());
            }
        } else {
            log.warn("物理文件不存在，仅删除数据库记录: {}", fullFilePath);
        }

        // 6. 更新数据库记录状态为已删除
        int updateResult = stgFileMapper.deleteById(stgFile);

        if (updateResult > 0) {
            log.info("文件删除成功: fileKey={}, fileName={}", fileKey, stgFile.getFileName());
            return true;
        } else {
            log.error("文件删除失败: fileKey={}", fileKey);
            return false;
        }
    }

    /**
     * 构建目录的完整物理路径
     *
     * @param basePath 基础路径
     * @param parentPath 父目录路径
     * @param directoryName 目录名称
     * @return 完整物理路径
     */
    private String buildDirectoryPath(String basePath, String parentPath, String directoryName) {
        if (StringUtils.hasText(parentPath)) {
            String cleanParentPath = parentPath.startsWith("/") ? parentPath.substring(1) : parentPath;
            return Paths.get(basePath, cleanParentPath, directoryName).toString();
        } else {
            return Paths.get(basePath, directoryName).toString();
        }
    }

    @Override
    public StgFile rename(String fileKey, String newFileName) {
        // 1. 根据fileKey查询文件记录
        QueryWrapper<StgFile> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(StgFile::getFileKey, fileKey)
                .eq(StgFile::getStatus, 1);

        StgFile stgFile = stgFileMapper.selectOne(queryWrapper);
        if (stgFile == null) {
            throw new RuntimeException("文件不存在或已被删除，fileKey: " + fileKey);
        }

        // 2. 根据sourceId获取存储源信息
        StgSource stgSource = stgSourceMapper.selectById(stgFile.getSourceId());
        if (stgSource == null) {
            throw new RuntimeException("存储源不存在，sourceId: " + stgFile.getSourceId());
        }

        if (stgSource.getIsEnabled() != 1) {
            throw new RuntimeException("存储源未启用，sourceId: " + stgFile.getSourceId());
        }

        // 3. 检查新文件名是否为空
        if (!StringUtils.hasText(newFileName)) {
            throw new RuntimeException("新文件名不能为空");
        }

        // 4. 检查同目录下是否已存在同名文件
        QueryWrapper<StgFile> checkWrapper = new QueryWrapper<>();
        checkWrapper.lambda()
                .eq(StgFile::getSourceId, stgFile.getSourceId())
                .eq(StgFile::getFilePath, stgFile.getFilePath())
                .eq(StgFile::getFileName, newFileName)
                .eq(StgFile::getStatus, 1);
        Long count = stgFileMapper.selectCount(checkWrapper);
        if (count > 0) {
            throw new RuntimeException("同目录下已存在同名文件: " + newFileName);
        }

        // 5. 构建旧文件的完整路径
        String oldFullPath = buildDownloadFilePath(stgSource, stgFile);
        File oldFile = new File(oldFullPath);

        // 6. 构建新文件的完整路径
        String oldFileName = stgFile.getFileName();
        stgFile.setFileName(newFileName);
        String newFullPath = buildDownloadFilePath(stgSource, stgFile);
        File newFile = new File(newFullPath);

        // 7. 重命名物理文件
        if (oldFile.exists()) {
            boolean renameSuccess = oldFile.renameTo(newFile);
            if (!renameSuccess) {
                throw new RuntimeException("重命名物理文件失败: " + oldFileName + " -> " + newFileName);
            }
            log.info("物理文件重命名成功: {} -> {}", oldFullPath, newFullPath);
        } else {
            log.warn("物理文件不存在，仅更新数据库记录: {}", oldFullPath);
        }

        // 8. 更新数据库记录
        int updateResult = stgFileMapper.updateById(stgFile);
        if (updateResult > 0) {
            log.info("文件重命名成功: fileKey={}, {} -> {}", fileKey, oldFileName, newFileName);

            // 9. 如果是目录类型，需要更新该目录下所有子文件和子目录的路径
            if ("directory".equals(stgFile.getFileType())) {
                updateChildrenFilePath(stgFile, oldFileName, newFileName);
            }

            return stgFile;
        } else {
            // 如果数据库更新失败，尝试回滚物理文件重命名
            if (newFile.exists()) {
                newFile.renameTo(oldFile);
                log.warn("数据库更新失败，已回滚物理文件重命名");
            }
            throw new RuntimeException("文件重命名失败: 数据库更新失败");
        }
    }

    /**
     * 构建目录在数据库中的路径
     *
     * @param parentPath 父目录路径
     * @param directoryName 目录名称
     * @return 数据库路径
     */
    private String buildDbPath(String parentPath, String directoryName) {
        if (StringUtils.hasText(parentPath)) {
            String cleanPath = parentPath.endsWith("/") ? parentPath : parentPath + "/";
            return cleanPath + directoryName + "/";
        } else {
            return "/" + directoryName + "/";
        }
    }

    /**
     * 更新目录下所有子文件和子目录的路径
     * 当目录重命名时，需要批量更新该目录下所有子孙项的filePath字段
     *
     * @param directory 被重命名的目录
     * @param oldDirectoryName 旧目录名
     * @param newDirectoryName 新目录名
     */
    private void updateChildrenFilePath(StgFile directory, String oldDirectoryName, String newDirectoryName) {
        // 1. 构建旧的目录路径和新的目录路径
        String oldDirectoryPath = buildDbPath(directory.getFilePath(), oldDirectoryName);
        String newDirectoryPath = buildDbPath(directory.getFilePath(), newDirectoryName);

        log.info("开始批量更新子文件路径: {} -> {}", oldDirectoryPath, newDirectoryPath);

        // 2. 使用LIKE查询该目录下的所有子孙文件和子目录（包括多层嵌套）
        QueryWrapper<StgFile> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(StgFile::getSourceId, directory.getSourceId())
                .likeRight(StgFile::getFilePath, oldDirectoryPath)
                .eq(StgFile::getStatus, 1);

        List<StgFile> allDescendants = stgFileMapper.selectList(queryWrapper);

        if (allDescendants.isEmpty()) {
            log.info("目录下没有子文件，无需更新路径");
            return;
        }

        // 3. 批量更新所有子孙项的filePath，将路径中的旧目录名替换为新目录名
        for (StgFile descendant : allDescendants) {
            String oldPath = descendant.getFilePath();
            // 将路径中的旧目录路径替换为新目录路径
            String newPath = oldPath.replaceFirst("^" + oldDirectoryPath.replace("/", "\\/"), newDirectoryPath);
            descendant.setFilePath(newPath);
            stgFileMapper.updateById(descendant);
            log.debug("更新子孙项路径: fileName={}, {} -> {}", descendant.getFileName(), oldPath, newPath);
        }

        log.info("子文件路径批量更新完成，共更新 {} 个子孙项", allDescendants.size());
    }

    /**
     * 自动检测本机IP地址
     * 根据IP前缀优先级列表选择最合适的IP地址
     *
     * @return 检测到的IP地址或主机名，如果检测失败则返回localhost
     */
    private String detectHostAddress() {
        try {
            // 获取所有网络接口
            List<InetAddress> allAddresses = new ArrayList<>();
            java.util.Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();

            while (networkInterfaces.hasMoreElements()) {
                NetworkInterface networkInterface = networkInterfaces.nextElement();

                // 跳过未启用或回环接口
                if (!networkInterface.isUp() || networkInterface.isLoopback()) {
                    continue;
                }

                // 获取该网络接口的所有IP地址
                java.util.Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
                while (inetAddresses.hasMoreElements()) {
                    InetAddress inetAddress = inetAddresses.nextElement();

                    // 只处理IPv4地址
                    if (inetAddress instanceof java.net.Inet4Address) {
                        allAddresses.add(inetAddress);
                    }
                }
            }

            // 如果没有找到任何IP地址，返回localhost
            if (allAddresses.isEmpty()) {
                log.warn("未检测到任何网络接口IP地址，使用默认值: localhost");
                return "localhost";
            }

            // 根据优先级列表选择最合适的IP地址
            for (String prefix : IP_PREFIX_PRIORITY) {
                for (InetAddress address : allAddresses) {
                    String hostAddress = address.getHostAddress();
                    if (hostAddress.startsWith(prefix)) {
                        log.info("根据优先级前缀 {} 选择IP地址: {}", prefix, hostAddress);
                        return hostAddress;
                    }
                }
            }

            // 如果没有匹配优先级列表的IP，返回第一个可用的IP
            String firstAddress = allAddresses.get(0).getHostAddress();
            log.info("未找到匹配优先级的IP地址，使用第一个可用IP: {}", firstAddress);
            return firstAddress;

        } catch (SocketException e) {
            log.error("检测IP地址时发生异常，使用默认值: localhost", e);
            return "localhost";
        }
    }
}

